/**
* URL.java 2020/5/9 5:10 下午
* Copyright ©2020 www.bmsoft.com.cn All rights reserved.
* PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package common.model;

import common.constants.CommonConstants;
import common.constants.RemotingConstants;
import common.util.CollectionUtils;
import common.util.NetUtils;
import common.util.StringUtils;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.ArrayUtils;

import static common.constants.CommonConstants.ANYHOST_KEY;
import static common.constants.CommonConstants.ANYHOST_VALUE;
import static common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
import static common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
import static common.constants.CommonConstants.GROUP_KEY;
import static common.constants.CommonConstants.HOST_KEY;
import static common.constants.CommonConstants.INTERFACE_KEY;
import static common.constants.CommonConstants.LOCALHOST_KEY;
import static common.constants.CommonConstants.PASSWORD_KEY;
import static common.constants.CommonConstants.PATH_KEY;
import static common.constants.CommonConstants.PORT_KEY;
import static common.constants.CommonConstants.PROTOCOL_KEY;
import static common.constants.CommonConstants.THREAD_NAME_KEY;
import static common.constants.CommonConstants.USERNAME_KEY;
import static common.constants.CommonConstants.VERSION_KEY;

/**
 * File：URL.java<br>
 * Title: <br>
 * Description: <br>
 * Company: www.treefinance.com.cn <br>
 * @author heyouchi
 * Date: 2020/5/9
 */
public class MagicUrl implements Serializable {

  private final String protocol;

  private final String username;

  private final String password;

  private final String host;

  private final int port;

  private final String path;

  private final Map<String, String> parameters;

  private volatile transient Map<String, Number> numbers;

  private volatile transient Map<String, MagicUrl> urls;

  private volatile transient String ip;

  private volatile transient String full;

  private volatile transient String identity;

  private volatile transient String string;

  private volatile transient String parameter;

  public MagicUrl(String protocol, String host, int port) {
    this(protocol, null, null, host, port, null, (Map<String, String>) null);
  }

  public MagicUrl(String protocol, String host, int port, String[] pairs) { // varargs ... conflict with the following path argument, use array instead.
    this(protocol, null, null, host, port, null, CollectionUtils.toStringMap(pairs));
  }

  public MagicUrl(String protocol, String host, int port, Map<String, String> parameters) {
    this(protocol, null, null, host, port, null, parameters);
  }

  public MagicUrl(String protocol, String host, int port, String path) {
    this(protocol, null, null, host, port, path, (Map<String, String>) null);
  }

  public MagicUrl(String protocol, String host, int port, String path, String... pairs) {
    this(protocol, null, null, host, port, path, CollectionUtils.toStringMap(pairs));
  }

  public MagicUrl(String protocol, String host, int port, String path, Map<String, String> parameters) {
    this(protocol, null, null, host, port, path, parameters);
  }

  public MagicUrl(String protocol, String username, String password, String host, int port, String path) {
    this(protocol, username, password, host, port, path, (Map<String, String>) null);
  }

  public MagicUrl(String protocol, String username, String password, String host, int port, String path, String... pairs) {
    this(protocol, username, password, host, port, path, CollectionUtils.toStringMap(pairs));
  }


  protected MagicUrl() {
    this.protocol = null;
    this.username = null;
    this.password = null;
    this.host = null;
    this.port = 0;
    this.path = null;
    this.parameters = null;
  }

  public MagicUrl(String protocol, String username, String password, String host, int port,
      String path, Map<String, String> parameters) {
    this.protocol = protocol;
    this.username = username;
    this.password = password;
    this.host = host;
    this.port = (port < 0 ? 0 : port);
    // trim the beginning "/"
    while (path != null && path.startsWith("/")) {
      path = path.substring(1);
    }
    this.path = path;
    if (parameters == null) {
      parameters = new HashMap<>();
    } else {
      parameters = new HashMap<>(parameters);
    }
    this.parameters = Collections.unmodifiableMap(parameters);
  }

  public static MagicUrl valueOf(String url) {
    if (url == null || (url = url.trim()).length() == 0) {
      throw new IllegalArgumentException("url == null");
    }
    String protocol = null;
    String username = null;
    String password = null;
    String host = null;
    int port = 0;
    String path = null;
    Map<String, String> parameters = null;
    int i = url.indexOf("?"); // separator between body and parameters
    if (i >= 0) {
      String[] parts = url.substring(i + 1).split("&");
      parameters = new HashMap<>();
      for (String part : parts) {
        part = part.trim();
        if (part.length() > 0) {
          int j = part.indexOf('=');
          if (j >= 0) {
            parameters.put(part.substring(0, j), part.substring(j + 1));
          } else {
            parameters.put(part, part);
          }
        }
      }
      url = url.substring(0, i);
    }
    i = url.indexOf("://");
    if (i >= 0) {
      if (i == 0) {
        throw new IllegalStateException("url missing protocol: \"" + url + "\"");
      }
      protocol = url.substring(0, i);
      url = url.substring(i + 3);
    } else {
      // case: file:/path/to/file.txt
      i = url.indexOf(":/");
      if (i >= 0) {
        if (i == 0) {
          throw new IllegalStateException("url missing protocol: \"" + url + "\"");
        }
        protocol = url.substring(0, i);
        url = url.substring(i + 1);
      }
    }

    i = url.indexOf("/");
    if (i >= 0) {
      path = url.substring(i + 1);
      url = url.substring(0, i);
    }
    i = url.lastIndexOf("@");
    if (i >= 0) {
      username = url.substring(0, i);
      int j = username.indexOf(":");
      if (j >= 0) {
        password = username.substring(j + 1);
        username = username.substring(0, j);
      }
      url = url.substring(i + 1);
    }
    i = url.lastIndexOf(":");
    if (i >= 0 && i < url.length() - 1) {
      if (url.lastIndexOf("%") > i) {
        // ipv6 address with scope id
        // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
        // see https://howdoesinternetwork.com/2013/ipv6-zone-id
        // ignore
      } else {
        port = Integer.parseInt(url.substring(i + 1));
        url = url.substring(0, i);
      }
    }
    if (url.length() > 0) {
      host = url;
    }
    return new MagicUrl(protocol, username, password, host, port, path, parameters);
  }

  public static MagicUrl valueOf(String url, String... reserveParams) {
    MagicUrl result = valueOf(url);
    if (reserveParams == null || reserveParams.length == 0) {
      return result;
    }
    Map<String, String> newMap = new HashMap<>(reserveParams.length);
    Map<String, String> oldMap = result.getParameters();
    for (String reserveParam : reserveParams) {
      String tmp = oldMap.get(reserveParam);
      if (StringUtils.isNotEmpty(tmp)) {
        newMap.put(reserveParam, tmp);
      }
    }
    return result.clearParameters().addParameters(newMap);
  }

  public MagicUrl setHost(String host) {
    return new MagicUrl(protocol, username, password, host, port, path, getParameters());
  }

  public String toFullString() {
    if (full != null) {
      return full;
    }
    return full = buildString(true, true);
  }

  public String toFullString(String... parameters) {
    return buildString(true, true, parameters);
  }

  private String buildString(boolean appendUser, boolean appendParameter, String... parameters) {
    return buildString(appendUser, appendParameter, false, false, parameters);
  }

  private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, boolean useService, String... parameters) {
    StringBuilder buf = new StringBuilder();
    if (StringUtils.isNotEmpty(protocol)) {
      buf.append(protocol);
      buf.append("://");
    }
    if (appendUser && StringUtils.isNotEmpty(username)) {
      buf.append(username);
      if (password != null && password.length() > 0) {
        buf.append(":");
        buf.append(password);
      }
      buf.append("@");
    }
    String host;
    if (useIP) {
      host = getIp();
    } else {
      host = getHost();
    }
    if (host != null && host.length() > 0) {
      buf.append(host);
      if (port > 0) {
        buf.append(":");
        buf.append(port);
      }
    }
    String path;
    if (useService) {
      path = getServiceKey();
    } else {
      path = getPath();
    }
    if (path != null && path.length() > 0) {
      buf.append("/");
      buf.append(path);
    }

    if (appendParameter) {
      buildParameters(buf, true, parameters);
    }
    return buf.toString();
  }

  /**
   * The format of return value is '{group}/{interfaceName}:{version}'
   * @return
   */
  public String getServiceKey() {
    String inf = getServiceInterface();
    if (inf == null) {
      return null;
    }
    return buildKey(inf, getParameter(GROUP_KEY), getParameter(VERSION_KEY));
  }

  public static String buildKey(String path, String group, String version) {
    StringBuilder buf = new StringBuilder();
    if (group != null && group.length() > 0) {
      buf.append(group).append("/");
    }
    buf.append(path);
    if (version != null && version.length() > 0) {
      buf.append(":").append(version);
    }
    return buf.toString();
  }


  private void buildParameters(StringBuilder buf, boolean concat, String[] parameters) {
    if (CollectionUtils.isNotEmptyMap(getParameters())) {
      List<String> includes = (ArrayUtils.isEmpty(parameters) ? null : Arrays.asList(parameters));
      boolean first = true;
      for (Map.Entry<String, String> entry : new TreeMap<>(getParameters()).entrySet()) {
        if (entry.getKey() != null && entry.getKey().length() > 0
            && (includes == null || includes.contains(entry.getKey()))) {
          if (first) {
            if (concat) {
              buf.append("?");
            }
            first = false;
          } else {
            buf.append("&");
          }
          buf.append(entry.getKey());
          buf.append("=");
          buf.append(entry.getValue() == null ? "" : entry.getValue().trim());
        }
      }
    }
  }

  public static MagicUrl valueOf(MagicUrl url, String[] reserveParams, String[] reserveParamPrefixs) {
    Map<String, String> newMap = new HashMap<>();
    Map<String, String> oldMap = url.getParameters();
    if (reserveParamPrefixs != null && reserveParamPrefixs.length != 0) {
      for (Map.Entry<String, String> entry : oldMap.entrySet()) {
        for (String reserveParamPrefix : reserveParamPrefixs) {
          if (entry.getKey().startsWith(reserveParamPrefix) && StringUtils.isNotEmpty(entry.getValue())) {
            newMap.put(entry.getKey(), entry.getValue());
          }
        }
      }
    }

    if (reserveParams != null) {
      for (String reserveParam : reserveParams) {
        String tmp = oldMap.get(reserveParam);
        if (StringUtils.isNotEmpty(tmp)) {
          newMap.put(reserveParam, tmp);
        }
      }
    }
    return newMap.isEmpty() ? new MagicUrl(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath())
        : new MagicUrl(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath(), newMap);
  }

  public String getProtocol() {
    return protocol;
  }

  public String getUsername() {
    return username;
  }

  public String getPassword() {
    return password;
  }

  public String getHost() {
    return host;
  }

  public int getPort() {
    return port;
  }

  public String getPath() {
    return path;
  }

  public String getIp() {
    if (ip == null) {
      ip = NetUtils.getIpByHost(host);
    }
    return ip;
  }

  public Map<String, String> getParameters() {
    return parameters;
  }


  public static String encode(String value) {
    if (StringUtils.isEmpty(value)) {
      return "";
    }
    try {
      return URLEncoder.encode(value, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  public boolean hasParameter(String key) {
    String value = getParameter(key);
    return value != null && value.length() > 0;
  }

  public String getParameter(String key, String defaultValue) {
    String value = getParameter(key);
    return StringUtils.isEmpty(value) ? defaultValue : value;
  }


  public List<String> getParameter(String key, List<String> defaultValue) {
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    String[] strArray = COMMA_SPLIT_PATTERN.split(value);
    return Arrays.asList(strArray);
  }

  public boolean getParameter(String key, boolean defaultValue) {
    String value = getParameter(key);
    return StringUtils.isEmpty(value) ? defaultValue : Boolean.parseBoolean(value);
  }

  public String getParameter(String key) {
    String value = parameters.get(key);
    return StringUtils.isEmpty(value) ? null : value;
  }

  public String getServiceInterface() {
    return getParameter(INTERFACE_KEY, path);
  }

  public int getParameter(String key, int defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.intValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    int i = Integer.parseInt(value);
    getNumbers().put(key, i);
    return i;
  }

  public MagicUrl setServiceInterface(String service) {
    return addParameter(INTERFACE_KEY, service);
  }

  public MagicUrl addParameterAndEncoded(String key, String value) {
    if (StringUtils.isEmpty(value)) {
      return this;
    }
    return addParameter(key, encode(value));
  }

  public MagicUrl addParameter(String key, boolean value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, char value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, byte value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, short value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, int value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, long value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, float value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, double value) {
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, Enum<?> value) {
    if (value == null) {
      return this;
    }
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, Number value) {
    if (value == null) {
      return this;
    }
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, CharSequence value) {
    if (value == null || value.length() == 0) {
      return this;
    }
    return addParameter(key, String.valueOf(value));
  }

  public MagicUrl addParameter(String key, String value) {
    if (StringUtils.isEmpty(key)
        || StringUtils.isEmpty(value)) {
      return this;
    }
    // if value doesn't change, return immediately
    if (value.equals(getParameters().get(key))) { // value != null
      return this;
    }

    Map<String, String> map = new HashMap<>(getParameters());
    map.put(key, value);
    return new MagicUrl(protocol, username, password, host, port, path, map);
  }

  public MagicUrl addParameterIfAbsent(String key, String value) {
    if (StringUtils.isEmpty(key)
        || StringUtils.isEmpty(value)) {
      return this;
    }
    if (hasParameter(key)) {
      return this;
    }
    Map<String, String> map = new HashMap<>(getParameters());
    map.put(key, value);
    return new MagicUrl(protocol, username, password, host, port, path, map);
  }

  /**
   * Add parameters to a new MagicUrl.
   *
   * @param parameters parameters in key-value pairs
   * @return A new MagicUrl
   */
  public MagicUrl addParameters(Map<String, String> parameters) {
    if (CollectionUtils.isEmptyMap(parameters)) {
      return this;
    }

    boolean hasAndEqual = true;
    for (Map.Entry<String, String> entry : parameters.entrySet()) {
      String value = getParameters().get(entry.getKey());
      if (value == null) {
        if (entry.getValue() != null) {
          hasAndEqual = false;
          break;
        }
      } else {
        if (!value.equals(entry.getValue())) {
          hasAndEqual = false;
          break;
        }
      }
    }
    // return immediately if there's no change
    if (hasAndEqual) {
      return this;
    }

    Map<String, String> map = new HashMap<>(getParameters());
    map.putAll(parameters);
    return new MagicUrl(protocol, username, password, host, port, path, map);
  }

  public MagicUrl addParametersIfAbsent(Map<String, String> parameters) {
    if (CollectionUtils.isEmptyMap(parameters)) {
      return this;
    }
    Map<String, String> map = new HashMap<>(parameters);
    map.putAll(getParameters());
    return new MagicUrl(protocol, username, password, host, port, path, map);
  }

  public MagicUrl addParameters(String... pairs) {
    if (pairs == null || pairs.length == 0) {
      return this;
    }
    if (pairs.length % 2 != 0) {
      throw new IllegalArgumentException("Map pairs can not be odd number.");
    }
    Map<String, String> map = new HashMap<>();
    int len = pairs.length / 2;
    for (int i = 0; i < len; i++) {
      map.put(pairs[2 * i], pairs[2 * i + 1]);
    }
    return addParameters(map);
  }


  public MagicUrl addParameterString(String query) {
    if (StringUtils.isEmpty(query)) {
      return this;
    }
    return addParameters(StringUtils.parseQueryString(query));
  }

  public MagicUrl removeParameter(String key) {
    if (StringUtils.isEmpty(key)) {
      return this;
    }
    return removeParameters(key);
  }

  public MagicUrl removeParameters(Collection<String> keys) {
    if (CollectionUtils.isEmpty(keys)) {
      return this;
    }
    return removeParameters(keys.toArray(new String[0]));
  }

  public MagicUrl removeParameters(String... keys) {
    if (keys == null || keys.length == 0) {
      return this;
    }
    Map<String, String> map = new HashMap<>(getParameters());
    for (String key : keys) {
      map.remove(key);
    }
    if (map.size() == getParameters().size()) {
      return this;
    }
    return new MagicUrl(protocol, username, password, host, port, path, map);
  }

  public MagicUrl clearParameters() {
    return new MagicUrl(protocol, username, password, host, port, path, new HashMap<>());
  }

  public String getRawParameter(String key) {
    if (PROTOCOL_KEY.equals(key)) {
      return protocol;
    }
    if (USERNAME_KEY.equals(key)) {
      return username;
    }
    if (PASSWORD_KEY.equals(key)) {
      return password;
    }
    if (HOST_KEY.equals(key)) {
      return host;
    }
    if (PORT_KEY.equals(key)) {
      return String.valueOf(port);
    }
    if (PATH_KEY.equals(key)) {
      return path;
    }
    return getParameter(key);
  }

  public String getMethodParameter(String method, String key) {
    String value = parameters.get(method + "." + key);
    return StringUtils.isEmpty(value) ? getParameter(key) : value;
  }

  public String getMethodParameter(String method, String key, String defaultValue) {
    String value = getMethodParameter(method, key);
    return StringUtils.isEmpty(value) ? defaultValue : value;
  }

  public double getMethodParameter(String method, String key, double defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.doubleValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    double d = Double.parseDouble(value);
    getNumbers().put(methodKey, d);
    return d;
  }

  public char getMethodParameter(String method, String key, char defaultValue) {
    String value = getMethodParameter(method, key);
    return StringUtils.isEmpty(value) ? defaultValue : value.charAt(0);
  }

  public boolean getMethodParameter(String method, String key, boolean defaultValue) {
    String value = getMethodParameter(method, key);
    return StringUtils.isEmpty(value) ? defaultValue : Boolean.parseBoolean(value);
  }

  private Map<String, Number> getNumbers() {
    // concurrent initialization is tolerant
    return numbers == null ? new ConcurrentHashMap<>() : numbers;
  }

  private Map<String, MagicUrl> getUrls() {
    // concurrent initialization is tolerant
    return urls == null ? new ConcurrentHashMap<>() : urls;
  }

  public InetSocketAddress toInetSocketAddress() {
    return new InetSocketAddress(host, port);
  }

  public MagicUrl setThreadName(String defaultName) {
    String name = getParameter(THREAD_NAME_KEY, defaultName);
    name = name + "-" + getAddress();
    return addParameter(THREAD_NAME_KEY, name);
  }

  public String getAddress() {
    return port <= 0 ? host : host + ":" + port;
  }

  public float getPositiveParameter(String key, float defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    float value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public double getPositiveParameter(String key, double defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    double value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public long getPositiveParameter(String key, long defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    long value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public int getPositiveParameter(String key, int defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    int value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public short getPositiveParameter(String key, short defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    short value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public byte getPositiveParameter(String key, byte defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    byte value = getParameter(key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public float getParameter(String key, float defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.floatValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    float f = Float.parseFloat(value);
    getNumbers().put(key, f);
    return f;
  }

  public double getParameter(String key, double defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.doubleValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    double d = Double.parseDouble(value);
    getNumbers().put(key, d);
    return d;
  }

  public short getParameter(String key, short defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.shortValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    short s = Short.parseShort(value);
    getNumbers().put(key, s);
    return s;
  }

  public byte getParameter(String key, byte defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.byteValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    byte b = Byte.parseByte(value);
    getNumbers().put(key, b);
    return b;
  }

  public long getParameter(String key, long defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.longValue();
    }
    String value = getParameter(key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    long l = Long.parseLong(value);
    getNumbers().put(key, l);
    return l;
  }


  public String[] getParameter(String key, String[] defaultValue) {
    String value = getParameter(key);
    return StringUtils.isEmpty(value) ? defaultValue : COMMA_SPLIT_PATTERN.split(value);
  }

  public double getMethodPositiveParameter(String method, String key, double defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    double value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public float getMethodPositiveParameter(String method, String key, float defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    float value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public long getMethodPositiveParameter(String method, String key, long defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    long value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public int getMethodPositiveParameter(String method, String key, int defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    int value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public short getMethodPositiveParameter(String method, String key, short defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    short value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }

  public byte getMethodPositiveParameter(String method, String key, byte defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    byte value = getMethodParameter(method, key, defaultValue);
    return value <= 0 ? defaultValue : value;
  }


  public float getMethodParameter(String method, String key, float defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.floatValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    float f = Float.parseFloat(value);
    getNumbers().put(methodKey, f);
    return f;
  }

  public long getMethodParameter(String method, String key, long defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.longValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    long l = Long.parseLong(value);
    getNumbers().put(methodKey, l);
    return l;
  }

  public int getMethodParameter(String method, String key, int defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.intValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    int i = Integer.parseInt(value);
    getNumbers().put(methodKey, i);
    return i;
  }

  public short getMethodParameter(String method, String key, short defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.shortValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    short s = Short.parseShort(value);
    getNumbers().put(methodKey, s);
    return s;
  }

  public byte getMethodParameter(String method, String key, byte defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.byteValue();
    }
    String value = getMethodParameter(method, key);
    if (StringUtils.isEmpty(value)) {
      return defaultValue;
    }
    byte b = Byte.parseByte(value);
    getNumbers().put(methodKey, b);
    return b;
  }

  public String getBackupAddress() {
    return getBackupAddress(0);
  }

  public String getBackupAddress(int defaultPort) {
    StringBuilder address = new StringBuilder(appendDefaultPort(getAddress(), defaultPort));
    String[] backups = getParameter(CommonConstants.BACKUP_KEY, new String[0]);
    if (ArrayUtils.isNotEmpty(backups)) {
      for (String backup : backups) {
        address.append(",");
        address.append(appendDefaultPort(backup, defaultPort));
      }
    }
    return address.toString();
  }


  static String appendDefaultPort(String address, int defaultPort) {
    if (address != null && address.length() > 0 && defaultPort > 0) {
      int i = address.indexOf(':');
      if (i < 0) {
        return address + ":" + defaultPort;
      } else if (Integer.parseInt(address.substring(i + 1)) == 0) {
        return address.substring(0, i + 1) + defaultPort;
      }
    }
    return address;
  }

  public String getAuthority() {
    if (StringUtils.isEmpty(username)
        && StringUtils.isEmpty(password)) {
      return null;
    }
    return (username == null ? "" : username)
        + ":" + (password == null ? "" : password);
  }


  public String toIdentityString() {
    if (identity != null) {
      return identity;
    }
    return identity = buildString(true, false); // only return identity message, see the method "equals" and "hashCode"
  }

  public String toIdentityString(String... parameters) {
    return buildString(true, false, parameters); // only return identity message, see the method "equals" and "hashCode"
  }


  public List<MagicUrl> getBackupUrls() {
    List<MagicUrl> urls = new ArrayList<>();
    urls.add(this);
    String[] backups = getParameter(RemotingConstants.BACKUP_KEY, new String[0]);
    if (backups != null && backups.length > 0) {
      for (String backup : backups) {
        urls.add(this.setAddress(backup));
      }
    }
    return urls;
  }

  public MagicUrl setAddress(String address) {
    int i = address.lastIndexOf(':');
    String host;
    int port = this.port;
    if (i >= 0) {
      host = address.substring(0, i);
      port = Integer.parseInt(address.substring(i + 1));
    } else {
      host = address;
    }
    return new MagicUrl(protocol, username, password, host, port, path, getParameters());
  }

  public MagicUrl setProtocol(String protocol) {
    return new MagicUrl(protocol, username, password, host, port, path, getParameters());
  }

  public String toServiceStringWithoutResolving() {
    return buildString(true, false, false, true);
  }

  public boolean isLocalHost() {
    return NetUtils.isLocalHost(host) || getParameter(LOCALHOST_KEY, false);
  }

  public boolean isAnyHost() {
    return ANYHOST_VALUE.equals(host) || getParameter(ANYHOST_KEY, false);
  }

  public MagicUrl setPath(String path) {
    return new MagicUrl(protocol, username, password, host, port, path, getParameters());
  }

  public static String decode(String value) {
    if (StringUtils.isEmpty(value)) {
      return "";
    }
    try {
      return URLDecoder.decode(value, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  /**
   * The format is "{interface}:[version]:[group]"
   * @return
   */
  public String getColonSeparatedKey() {
    StringBuilder serviceNameBuilder = new StringBuilder();
    append(serviceNameBuilder, INTERFACE_KEY, true);
    append(serviceNameBuilder, VERSION_KEY, false);
    append(serviceNameBuilder, GROUP_KEY, false);
    return serviceNameBuilder.toString();
  }

  private void append(StringBuilder target, String parameterName, boolean first) {
    String parameterValue = this.getParameter(parameterName);
    if (!StringUtils.isBlank(parameterValue)) {
      if (!first) {
        target.append(":");
      }
      target.append(parameterValue);
    } else {
      target.append(":");
    }
  }

  public String getParameterAndDecoded(String key) {
    return getParameterAndDecoded(key, null);
  }

  public String getParameterAndDecoded(String key, String defaultValue) {
    return decode(getParameter(key, defaultValue));
  }

  @Override
  public String toString() {
    if (string != null) {
      return string;
    }
    return string = buildString(false, true); // no show username and password
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((host == null) ? 0 : host.hashCode());
    result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
    result = prime * result + ((password == null) ? 0 : password.hashCode());
    result = prime * result + ((path == null) ? 0 : path.hashCode());
    result = prime * result + port;
    result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
    result = prime * result + ((username == null) ? 0 : username.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    MagicUrl other = (MagicUrl) obj;
    if (host == null) {
      if (other.host != null) {
        return false;
      }
    } else if (!host.equals(other.host)) {
      return false;
    }
    if (parameters == null) {
      if (other.parameters != null) {
        return false;
      }
    } else if (!parameters.equals(other.parameters)) {
      return false;
    }
    if (password == null) {
      if (other.password != null) {
        return false;
      }
    } else if (!password.equals(other.password)) {
      return false;
    }
    if (path == null) {
      if (other.path != null) {
        return false;
      }
    } else if (!path.equals(other.path)) {
      return false;
    }
    if (port != other.port) {
      return false;
    }
    if (protocol == null) {
      if (other.protocol != null) {
        return false;
      }
    } else if (!protocol.equals(other.protocol)) {
      return false;
    }
    if (username == null) {
      if (other.username != null) {
        return false;
      }
    } else if (!username.equals(other.username)) {
      return false;
    }
    return true;
  }
  public String toParameterString() {
    if (parameter != null) {
      return parameter;
    }
    return parameter = toParameterString(new String[0]);
  }

  public String toParameterString(String... parameters) {
    StringBuilder buf = new StringBuilder();
    buildParameters(buf, false, parameters);
    return buf.toString();
  }

}
